#!/usr/bin/python
#-*-coding: utf-8 -*-

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

u"""
This module provides functions for simple and frequent tasks like type 
conversions or type check.
"""

__all__ = [
           u'CREATED_PATHS',
           u'Stopwatch',
           u'Utils',
           u'age_to_date',
           u'arrange_alias',
           u'arrange_path',
           u'check_ascii',
           u'check_bool',
           u'check_iterable',
           u'check_keys',
           u'check_pyvar_naming_convention',
           u'check_type',
           u'check_value',
           u'check_var_naming_convention',
           u'checktopath',
           u'cmp_path_date',
           u'dict_to_ordered_items',
           u'error',
           u'find_file',
           u'get_data',
           u'get_datatest',
           u'get_text_file_format',
           u'good_level',
           u'good_level_2',
           u'is_binary',
           u'is_iterable',
           u'list2',
           u'list_to_unicode_list',
           u'load_plugins_into_module',
           u'make_alias',
           u'mkdtempu',
           u'str2unicode',
           u'to_qt_tt',
           u'to_xml',
           u'unicode2',
           u'unicode2_to_ASCIIstr',
           u'unicode2_to_str',
           u'unicode_list_to_float_list',
           u'uprint',
           u'walk_files',
           ]

import re
import sys
import time
import codecs
import pickle
import tempfile
import datetime
import pkg_resources

from _pylot.minimal.path import Path, DirPath, FilePath
from _pylot.minimal.system import ENCODING, error, uprint
from _pylot.data import regexpr

def arrange_path(path):
  if not path :
    return None
  path = Path(unicode(path))
  if path.isfile():
    path = FilePath(path)
  elif path.isdir():
    path = DirPath(path)
  return path

def age_to_date(minutes, hours=0, days=0):
  today = datetime.datetime.today()
  delta = datetime.timedelta(days=days, hours=hours, minutes=minutes)
  return today - delta

def cmp_path_date(path1, path2):
  return cmp(path1.mtime_datetime(), path2.mtime_datetime())

def to_xml (text):
  from xml.dom.minidom import Document
  doc = Document()
  if isinstance(text, str):
    text = text.decode(ENCODING)
  elif isinstance(text, unicode):
    pass
  else :
    if text :
      text = unicode(text)
    else :
      return u''
  return doc.createTextNode(text).toxml()

def to_qt_tt(text):
  if isinstance(text, bool) :
    if text is True :
      return u'checked'
    else :
      return u'unchecked'
  else :
    return to_xml(text)

def mkdtempu (suffix=u'', prefix=u'tmp', dir=None):
  u"""
  Like :meth:`tempfile.mkdtemp` but returns :class:`~pyLot.core.Path` instead
  of :obj:`str` and takes unicode parameters instead of str.
  """
  check_type(u'suffix', suffix, unicode)
  check_type(u'prefix', prefix, unicode)

  suffix = suffix.encode(ENCODING, u'replace')
  prefix = prefix.encode(ENCODING, u'replace')
  path = tempfile.mkdtemp(suffix, prefix, dir)
  upath = Path(path.decode(ENCODING)).abspath()
  assert(unicode(upath).encode(ENCODING) == path)
  return upath

def make_alias (obj) :
  if obj.alias :
    return obj.alias
  else :
    return obj.name.capitalize()

def list_to_unicode_list (src):
  check_type(u'src', src, list)
  new_list = []
  for i, val in enumerate(src) :
    if isinstance(val, list) :
      new_list.append(list_to_unicode_list(val))
    elif isinstance(val, str) :
      new_list.append(val.decode(ENCODING))
    else :
      new_list.append(unicode(val))
  return new_list

def unicode_list_to_float_list (src):
  u"""
  
  Example :

  >>> unicode_list_to_float_list([u'1', 2, [u'3', u'4']])
  [1.0, 2.0, [3.0, 4.0]]
  """
  check_type(u'src', src, list)
  for i, val in enumerate(src) :
    if isinstance(val, list) :
      src[i] = unicode_list_to_float_list(val)
    else :
      src[i] = float(val)
  return src

def str2unicode(s, encoding=ENCODING):
  u"""
  Converts s into unicode string using encoding.
  If s is None, returns None.
  """
  if s is None :
    return None
  else :
    return s.decode(ENCODING)

def unicode2(s):
  u"""
  returns u'' if s is None else unicode(s)
  """
  if isinstance(s, unicode) :
    return s
  elif s is None :
    return u''
  else :
    return str(s).decode(ENCODING, u'replace')

def unicode2_to_str (s):
  u"""
  Like unicode2 but returns str instead of unicode
  """
  if isinstance(s, unicode) :
    return s.encode(ENCODING, u'replace')
  elif s is None :
    return ''
  else :
    return str(s)

def unicode2_to_ASCIIstr (s):
  u"""
  Like unicode2 but returns ASCII str instead of unicode
  """
  if isinstance(s, unicode) :
    return s.encode(u'ASCII', u'replace')
  elif s is None :
    return ''
  else :
    return str(s)

def list2(lst):
  u"""
  returns [] if s is None else list(s)
  """
  if lst is None:
    return []
  else:
    return list(lst)

def check_iterable (varname, value):
  u"""
  >>> check_iterable(u'var', [])
  >>> check_iterable(u'var', 1)
  Traceback (most recent call last):
  ...
  TypeError: E001 - var: iterable object expected, got int
  >>> check_iterable(u'var', u"abc")
  Traceback (most recent call last):
  ...
  TypeError: E001 - var: iterable object expected, got unicode
  """
  if type(varname) != unicode :
    raise TypeError, error(tr_(u'E001 - varname: unicode expected, got %(TYPE)s') % dict(
      TYPE=type(varname).__name__))
  if not isinstance(value, (set, list, tuple)) :
    raise TypeError, error(tr_(u'E001 - %(VARNAME)s: iterable object expected, '\
      u'got %(TYPE)s') % dict(VARNAME=varname, TYPE=type(value).__name__))

def check_implements (varname, value, interface_list, subclass=True, none=False,
                exception=None):
  pass

def checktopath(path):
  u"""
  Vérifie le type et renvoie un chemin
  """
  check_type(u'path', path, unicode)
  return Path(path)

def check_bool(varname, value, none=False):
  if isinstance(value, unicode):
    value = value.strip().lower()
    if value in(u'true', u'on', u'1') :
      return True
    elif value in(u'false', u'off', u'0') :
      return False
    else :
      return check_type(varname, value, bool, none=none)

  else :
    return check_type(varname, value, bool, none=none)

def check_type (varname, value, type_list, subclass=True, none=False,
                exception=None):
  u"""
  Checks parameter type.
  
  :param varname: variable name, the name displayed in error messages. Useful for debug
  :type varname: `unicode`
  :param value: value to test (the variable)
  :type value: all types
  :param type_list: list of allowed types
  :type type_list: `list` or type
  :subclass: if True[default], accept subclasses of allowed types.
  :type subclass: `bool`
  :param none: if True, also accept None value (NoneType type)
  :type none: `bool`
  """
  if type(varname) != unicode :
    raise TypeError, error(tr_(u'E005 - varname: unicode expected, got %(TYPE)s') % dict(
      TYPE=type(varname).__name__))

  if type(subclass) != bool :
    raise TypeError, error(tr_(u'E001 - subclass: bool expected, got %(TYPE)s') % dict(
      TYPE=type(subclass).__name__))

  if type(none) != bool :
    raise TypeError, error(tr_(u'E001 - none: bool expected, got %s(TYPE)') % dict(
      TYPE=type(none).__name__))

  if is_iterable(type_list) :
    type_list = list(type_list)
  else :
    type_list = [type_list]

  if none is True :
    type_list.append(type(None))

  matches = False
  if subclass is True :
    for ttype in type_list :
      if isinstance(value, ttype) :
        matches = True
        break
      else :
        continue
  else :
    for ttype in type_list :
      if type(value) is ttype :
        matches = True
        break
      else :
        continue

  if not matches :
    type_name_list = [unicode(ttype.__name__) for ttype in type_list]
    type_name_list.sort()
    dic = dict(varname=varname, type=type(value).__name__, value=value)
    if exception :
      raise exception(**dic)
      return
    if type_name_list == [u'Path', u'unicode'] or type_name_list == [u'NoneType', u'Path', u'unicode'] :
      ERROR = u'E006'
    elif type_name_list == [u'unicode'] :
      ERROR = u'E005'
    else :
      ERROR = u'E001'

    type_names = u', '.join(type_name_list)
    dic['TYPES'] = type_names
    dic['ERROR'] = ERROR
    dic['VARNAME'] = varname
    dic['TYPE'] = type(value).__name__
    raise TypeError, error(tr_(u'%(ERROR)s - %(VARNAME)s: %(TYPES)s expected, got %(TYPE)s') % dic)

  return value

#def check_type (varname, value, type_list, subclass=True, none=False,
#                exception=None):
#  return value

def check_value(varname, value, value_list=[]):
  u"""
  checks parameter values

  Example :

  >>> check_value(u'mesh_dim', -1, [1, 2, 3])
  Traceback (most recent call last):
  ...
  ValueError: E002 - mesh_dim: 1, 2, 3 expected, got -1

  :param varname: variable name, useful for debug
  :param value: value to test
  :param value_list: list of allowed values
  """
  if is_iterable(value_list) :
    value_list = list(value_list)
  else :
    value_list = [value_list]

  if value not in value_list :
    values = u', '.join([repr(val).decode(ENCODING, u'replace') for val in value_list])
    raise ValueError, error(tr_(u'E002 - %(VARNAME)s: %(VALUES)s expected, got %(VALUE)s') % dict(
      VARNAME=varname, VALUES=values, VALUE=repr(value).decode(ENCODING, u'replace')))

def check_keys(varname, dic, keys):
  check_type(u'varname', varname, unicode)
  check_type(u'dic', dic, dict)
  check_type(u'keys', keys, list)
  for key in keys :
    if not dic.has_key(key) :
      raise ValueError, error(tr_(u'dict "%(NAME)s" must contains keys : %(KEYS)s. key "%(KEY)s" is missing') % (
        dict(NAME=varname, KEYS=u', '.join(keys), KEY=key)))

def good_level_2(current_level, option_level):
  level_dict = {u'hidden':5, u'expert':4, u'user':1, u'devel':2}
  return good_level(option_level, level_dict, current_level)

def good_level (option_level, level_dict, current_level):
  u"""
  Returns True if manager level is greater or equal than given level

  Order is defined by level dict.
  Ex1 (user interface level) : 
    level_dict = {'user':1, 'devel':2, 'devel':3, 'expert':4}
  Ex2 (display level) : 
    level_dict = {'silent':1, 'standard':2, 'verbose':3}
  
  The greater, the more (displayed, done, ...)

  For example, if we want to try an devel option :

  if good_level('devel', level_dict, current_level) :
    print "only in user devel and higher"
    ...

  The answer depends on current interface level :
    if current_level is "user", return False
                        "devel-user", return True
                        "devel", return True
                        "expert", return True

  :param option_level: level required, must be a string supported by level_dict 
  (ex: user)
  :type option_level: ASCII string
  """
  if type(option_level) is unicode :
    if option_level not in level_dict.keys() :
      uprint(tr_(u'level %s not available, consider option not available') % option_level,
             file=sys.stderr)
      return False
    else :
      option_level = level_dict[option_level]
  elif type(option_level) is bool :
    uprint(tr_(u'level %s not available, consider option not available') % option_level,
           file=sys.stderr)
  if level_dict[current_level] >= option_level :
    return True
  else :
    return False



def is_binary(filename):
  """Return true if the given filename is binary.
  @raise EnvironmentError: if the file does not exist or cannot be accessed.
  @attention: found @ http://bytes.com/topic/python/answers/21222-determine-file-type-binary-text on 6/08/2010
  @author: Trent Mick <TrentM@ActiveState.com>
  @author: Jorge Orpinel <jorge@orpinel.com>"""
  fin = open(filename, 'rb')
  try:
    CHUNKSIZE = 1024
    while True:
      chunk = fin.read(CHUNKSIZE)
      if '\0' in chunk: # found null byte
        return True
      if len(chunk) < CHUNKSIZE:
        break # done
  # A-wooo! Mira, python no necesita el "except:". Achis... Que listo es.
  finally:
    fin.close()

  return False

def get_text_file_format(path, nmax=20):
  lines = [None for i in range(nmax)]
  if is_binary(path) :
    fmt = 'bin'
  else :
    f = open(path, 'r')
    lines[0] = l = f.readline()
    if l.endswith('\r\n') :
      fmt = 'win'
    elif l.endswith('\r') :
      fmt = 'mac'
    elif l.endswith('\n') :
      fmt = 'nix'
    else :
      fmt = 'other'
  return fmt



def check_ascii (varname, value):
  u"""
  checks if value contains only ASCII characters

  :param varname: variable name, useful for debug
  :param value: value to test
  """
  if type(varname) != unicode :
    raise TypeError, error(tr_(u'E001 - varname: unicode expected, got %(TYPE)s') % dict(
      TYPE=type(varname).__name__))
  if not isinstance(value, basestring) :
    raise TypeError, error(tr_(u'E001 - value: str or unicode expected, got  %(TYPE)s') % dict(
      TYPE=type(varname).__name__))

  if isinstance(value, str) :
    try :
      value.decode(u'ascii')
    except UnicodeEncodeError :
      raise ValueError, error(tr_(u'E003 - %(STRING)s: ASCII string required') % dict(
        STRING=varname))
  elif isinstance(value, unicode) :
    try :
      value.encode(u'ascii')
    except UnicodeEncodeError :
      raise ValueError, error(tr_(u'E003 - %(STRING)s: ASCII string required') % dict(
        STRING=varname))

def _check_var_naming_convention (varname, pattern, conv_name):
  u"""
  :param varname: string to test
  :type varname: str or unicode
  :param pattern: compiled regular expression, see `re` module
  :type pattern: pattern object
  :param conv_name: naming convention name (ex: python, CMake, ...)
  :type conv_name: unicode 
  """
  check_ascii(u'varname', varname)
  match_obj = pattern.match(varname)
  if match_obj :
    if match_obj.group() != varname : # not allowed separator like "," found
      raise ValueError, \
      error(tr_(u"E004 - name '%(NAME)s' must respect %(CONVENTION)s "\
         u"variable naming conventions") % dict(
          NAME=varname, CONVENTION=conv_name))
  else :
    raise ValueError, \
      error(tr_(u"E004 - name '%(NAME)s' must respect %(CONVENTION)s "\
         u"variable naming conventions") % dict(
          NAME=varname, CONVENTION=conv_name))

def check_var_naming_convention (varname):
  _check_var_naming_convention(varname, regexpr.co_varname, u'pyLot')

def check_pyvar_naming_convention (varname):
  _check_var_naming_convention(varname, regexpr.co_pyvarname, u'python')

def check_stream (varname, value):
  u"""
  currently do nothing
  """
  return

def is_iterable (obj):
  u"""
  :return: True if obj can be iterated (ie is list, tuple, set, ...)
  """
  return issubclass(type(obj), (set, tuple, list))

def dict_to_ordered_items (dic):
  u"""
  Use this function to keep a constant order for a dict

  >>> d = dict(b=2, c=1, a=3)
  >>> d.items() # arbitrary order
  [('a', 3), ('c', 1), ('b', 2)]

  >>> dict_to_ordered_items(d)
  [('a', 3), ('b', 2), ('c', 1)]

  The order correspond to constant key order a, b, c
  """
  check_type(u'dic', dic, dict)

  keys = dic.keys()
  keys.sort()
  lst = []
  for key in keys :
    lst.append((key, dic[key]))
  return lst

def arrange_alias (identifier, alias):
  check_type(u'identifier', identifier, unicode, none=False)
  check_var_naming_convention(identifier)
  check_type(u'alias', alias, unicode, none=True)

  if alias is None :
    return identifier
  else :
    return unicode(alias)

import xml.sax
def parse_xml (file_or_stream, handler):
  parser = xml.sax.make_parser()
  parser.setContentHandler(handler)
  parser.setFeature(xml.sax.handler.feature_namespaces, 0)
  parser.setFeature(xml.sax.handler.feature_external_ges, False)
  parser.parse(file_or_stream)
  return handler

def load_plugins_into_module (plugin_paths, module,
                           mod_exts=(u'.py', u'.pyc', u'.pyd', u'.pyo'),
                           display_level=u'standard'):
  u"""
  Load dynamically all modules found in plugin_paths list and add it to
  module.
  """
  check_iterable(u'plugin_paths', plugin_paths)
  check_iterable(u'mod_exts', mod_exts)
  for ext in mod_exts :
    check_type(u'extension', ext, unicode)
  check_type(u'display_level', display_level, unicode)

  for plugin_path in plugin_paths :
    plugin_path = Path(plugin_path).abspath()
    if plugin_path.isdir() :
      if plugin_path.encode(ENCODING, u'ignore') not in sys.path :
        sys.path.append(str(plugin_path))

      for path in plugin_path.listdir() :
        modname = path.namebase
        if (path.isfile() and path.ext not in mod_exts) \
           or modname == u'__init__' \
           or modname.startswith(u'.') \
           or not modname.strip() :
          pass
        else :
          try :
            mod = __import__(modname)
          except ImportError :
            pass
          else :
            setattr(module, modname, mod)
            if display_level == u'verbose' :
              uprint(tr_(u'plugin %s loaded successfully (%s)') % (modname, mod))

def get_datatest(filename):
  return get_data(u'doctests', u'tests/test_data/%s' % filename)

def get_data(name, path):
  """
  :param name: __name__
  """
  path = pkg_resources.resource_filename(name, path)
  path = Path(path).abspath()
  return path

def find_file (relpath, dirlist):
  u"""
  Find "relpath" in dirlist directories.
  findFile is NOT recursive

  ex : cls.findFile('python.pdf', dirlist=['/usr/share/doc', '/home/guillaume/doc'])
  -> /home/guillaume/doc/python.pdf

  :param relpath: file you want to search relatively to dirlist
  :type relpath: str or :class:`~_pylot.minimal.Path`

  :param dirlist: list of directory which can contain relpath
  :type dirlist: str or :class:`~_pylot.minimal.Path`
  """
  check_type(u'relpath', relpath, [unicode, Path])

  if not is_iterable(dirlist) :
    dirlist = (dirlist,)
  filepath = None
  for directory in dirlist :
    fp = Path(directory, relpath)
    if fp.exists() :
      filepath = fp
      break
  if type(filepath) is Path :
    return filepath.abspath()
  else :
    return filepath

def walk_files (path, pattern=u'*'):
  u"""
  ex:
  walk_files(u'/path', u'*.py')
  [u'f1.py', u'autre.py']
  walk_files('/path/autre.py', u'*.txt')
  []
  walk_files('/path/autre.py', u'*.py')
  [u'autre.py']
  walk_files(u'/not/exists/', u'*.txt')
  []

  :param path: path to walk in. Can be file or directory path.
  :type path: L{Path<_pylot.minimal.path.Path>} or str
  :param pattern: a simple pattern like '*.txt', 'README*', ...
  :type pattern: unicode

  :return: matching file list
  :rtype: list
  """
  check_type(u'path', path, [unicode, Path, list, tuple, set])
  check_type(u'pattern', pattern, unicode)

  all_path = set([])
  if isinstance(path, (list, tuple, set)) :
    pathlist = path
  else :
    pathlist = [path]

  for path in pathlist :
    path = Path(path)
    if path.isfile() :
      all_path.add(path.abspath())
    elif path.isdir() :
      for p in path.walkfiles(pattern) :
        all_path.add(p.abspath())
  all_path = list(all_path)
  all_path.sort()
  return all_path

class Utils (object):
  u"""
  Define usefull classmethods.
  All methods of this class can be used in derivated class AND/OR externally
  """
  @classmethod
  def defaultPath (cls, path):
    check_type(u'path', path, (unicode, Path), none=True)
    if path :
      return Path(path).abspath()
    else :
      return Path(u'.').abspath()

  @classmethod
  def searchInStream (cls, stream, reg_expr=None, strip=False):
    u"""
    return a list of string matching pattern

    :param stream: the stream you want to search in
    :type stream: obj supporting "for x in"

    :param reg_expr: regular expression (see "re" module)
    :type reg_expr: str

    :param strip: strip line before seeking
    :type strip: bool
    """
    check_stream(u'stream', stream)
    check_type(u'reg_expr', reg_expr, unicode, none=True)
    check_type(u'strip', strip, bool)

    result = []
    if reg_expr and stream :
      reg = re.compile(reg_expr)
      if type(stream) is unicode :
        if strip : stream = stream.strip()
        result += reg.findall(stream)
      else :
        for line in stream :
          if strip : line = line.strip()
          result += reg.findall(stream)
    return result

  @classmethod
  def normalizeLineSeparator (cls, string):
    check_type(u'string', string, unicode)

    for linesep in (u'\r\n', u'\r\x85', u'\r', u'\x85', u'\u2028') :
      if linesep in string :
        string = string.replace(linesep, u'\n')
    return string

  @classmethod
  def searchLinesInStream (cls, stream, string=None, reg_expr=None, strip=False):
    u"""
    like searchInStream but return a list of lines containing pattern
    """
    check_stream(u'stream', stream)
    check_type(u'string', string, unicode)
    check_type(u'reg_expr', reg_expr, unicode, none=True)
    check_type(u'strip', strip, bool)

    result = []
    if type(stream) is unicode :
      stream = cls.normalizeLineSeparator(stream)
      stream = stream.split(u'\n')
    if reg_expr and stream :
      reg = re.compile(reg_expr)
      for line in stream :
        if strip : line = line.strip()
        if reg.search(line) :
          result.append(line)
    if string and stream:
      for line in stream :
        if strip : line = line.strip()
        if string in line :
          result.append(line)
    return result

  @classmethod
  def walkFiles (cls, path, pattern=u'*'):
    u"""
    ex:
    Utils.walkFiles('/path', '*.py')
    ['f1.py', 'autre.py']
    Utils.walkFiles('/path/autre.py', '*.txt')
    ['autre.py']
    Utils.walkFiles('/not/exists/', '*.txt')
    []

    :param path: path to walk in. Can be file or directory path.
    :type path: L{Path<pylot.core.path.Path>} or str
    :param pattern: a simple pattern like '*.txt', 'README*', ...
    :type pattern: str

    :return: matching file list
    :rtype: list
    """
    check_type(u'path', path, [unicode, Path, list, tuple, set])
    check_type(u'pattern', pattern, unicode)

    all_path = set([])
    if isinstance(path, (list, tuple, set)) :
      pathlist = path
    else :
      pathlist = [path]

    for path in pathlist :
      path = Path(path)
      if path.isfile() :
        all_path.add(path.abspath())
      elif path.isdir() :
        for p in path.walkfiles(pattern) :
          all_path.add(p.abspath())
    all_path = list(all_path)
    all_path.sort()
    return all_path

  @classmethod
  def copyFile (cls, src, dest):
    check_type(u'src', src, [unicode, Path])
    check_type(u'dest', dest, [unicode, Path])

    src = Path(src).abspath()
    if src :
      dest = Path(dest).abspath()
      if dest.isfile() : dest.remove()
      elif dest.isdir() : dest = Path(dest, src.name)
      elif not dest.exists() :
        if src.name == dest.name and not dest.parent.exists() :
          dest.parent.makedirs()
        elif src.name != dest.name and not dest.exists() :
          dest.makedirs()
          dest = Path(dest, src.name)

      src.copyfile(dest)
      return dest
    else :
      raise ValueError, error(tr_(u'file %s not exists') % src)

  @classmethod
  def safeReplaceable (cls, path):
    u"""
    if return True, you can replace this path because nothing exists
    or a backup has been done. Else, function return False
    """
    check_type(u'path', path, [unicode, Path])
    safe = False
    path = Path(path)
    if path.exists() :
      safe = cls.backupFile(path)
    else : safe = True

    return bool(safe)

  @classmethod
  def backupFile (cls, filename, backup_dir=None):
    u"""
    Backups file `filename`

    filename is copied to filename-01, if filename-01 exists, it's copied
    to filename-02, ...

    :param filename: file you want to backup
    :type filename: str or :class:`~_pylot.minimal.Path`

    :param backup_dir: the directory where you want to backup file. Default: filename directory
    :type backup_dir: :obj:`None`, :obj:`unicode` or :class:`~_pylot.minimal.Path`

    :return: backuped file path or None if backup fail
    :rtype: :class:`~_pylot.minimal.Path` or None if copy fails
    """
    check_type(u'filename', filename, [unicode, Path])
    check_type(u'backup_dir', backup_dir, [unicode, Path], none=True)

    filename = Path(filename)
    if backup_dir is None :
      save = cls.suggestName(filename)
    else :
      backup_dir = Path(backup_dir)
      save = cls.suggestName(Path(backup_dir, filename.name))
      if not backup_dir.exists() :
        backup_dir.makedirs()
    try :
      filename.copy(save)
    except (IOError, OSError) :
      return None
    else :
      return save

  @classmethod
  def suggestName (cls, filename):
    check_type(u'filename', filename, [unicode, Path])

    filename = Path(filename)
    if not filename.exists() :
      save = filename
    else :
      i = 1
      save = Path(u'%s-%02d' % (filename, i))
      while save.exists() :
        i += 1
        save = Path(save.replace(unicode(i - 1), unicode(i)))
    return save

  @classmethod
  def readfile (cls, filename, encoding=u'utf-8'):
    u"""
    :param encoding: file encoding. if None, file is opened in byte mode instead of unicode mode
    """
    check_type(u'filename', filename, [unicode, Path])
    check_type(u'encoding', encoding, unicode, none=True)

    filename = Path(filename)
    try :
      if encoding :
        return codecs.open(filename.abspath(), u'r', encoding)
      else :
        return open(filename.abspath(), u'r')
    except IOError :
      raise IOError, error(tr_(u'cannot read file %s') % filename.abspath())

  @classmethod
  def writefile (cls, filename, security=u'safe', mode=u'w', encoding=u'utf-8'):
    u"""
    :param encoding: file encoding. if None, file is opened in byte mode instead of unicode mode
    """
    check_type(u'filename', filename, [unicode, Path])
    check_type(u'security', security, unicode)
    check_value(u'security', security, [u'force', u'default', u'safe'])
    check_ascii(u'mode', mode)
    check_type(u'encoding', encoding, unicode)

    crashfile = None
    write = False
    filename = Path(filename).abspath()
    if filename.exists() :
      if security == u'force' :
        write = True
  #      print >> sys.stderr, 'file %s replaced' %filename.abspath()
      elif security == u'default' :
        uprint(u'file %s exists, replace' % filename.abspath(), file=sys.stderr)
        if raw_input(u' ').lower() in (u'y', u'yes') :
          write = True
      else :
        uprint(u'file %s exists, nothing done' % filename.abspath(), file=sys.stderr)
    else :
      write = True

    if write :
      if not filename.parent.exists() :
        try :
          filename.parent.makedirs()
        except OSError :
          uprint(u'cannot create parent dir', file=sys.stderr)
          raise OSError
          openfile = crashfile
        else :
          pass
      try :
        if encoding is None :
          openfile = open(filename.abspath(), mode)
        else :
          openfile = codecs.open(filename.abspath(), mode, encoding)
      except IOError:
        uprint(u'cannot write %s, redirect to stdout' % filename.abspath(), file=sys.stderr)
        raise IOError
        openfile = crashfile
      else :
        pass
    else :
      raise OSError
      openfile = crashfile
    return openfile

  @classmethod
  def _arrangePath (cls, path):
    assert(isinstance(path, Path))
    if not path.exists() :
      try : path.makedirs()
      except OSError :
        uprint(u'cannot make directory %s' % path, file=sys.stderr)
        path = None
    else :
      fpath = Path(path, u'tmptestfile')
      try : f = open(fpath, u'w')
      except IOError:
        uprint(u'Permission denied: %s' % path, file=sys.stderr)
        path = None
      else :
        f.close()
        fpath.remove()

    return path

class Stopwatch (object):
  u"""
  Simple stopwatch
  
  >>> t = Stopwatch()
  >>> float(t)
  -1.0
  >>> t.start()
  >>> float(t) #doctest:+SKIP
  0.0002
  >>> float(t) #doctest:+SKIP
  0.0004
  >>> t.stop()
  >>> tend = float(t)
  >>> import time ; time.sleep(1)
  >>> float(t) == tend
  True
  """
  def __init__ (self):
    object.__init__(self)
    self._start_time = None
    self._stop_time = None

  def _gettime (self):
    u"""
    if not running, return -1
    if running, return time since start time
    if stopped, return execution time
    """
    if self._start_time is None :
      return -1
    elif self._stop_time is None :
      return time.time() - self._start_time
    else :
      return self._stop_time - self._start_time

  time = property(fget=_gettime)

  def start (self):
    u"""
    Start stopwatch. If stopwatch is running, stop it, reset time and start
    """
    self._stop_time = None
    self._start_time = time.time()

  def stop (self):
    u"""
    Stop stopwatch. If not running, reset stopwatch
    """
    if self._start_time is None :
      self._stop_time = None
    else :
      self._stop_time = time.time()

  def reset (self):
    self._start_time = None
    self._stop_time = None

  def __float__ (self):
    return float(self.time)

  def __int__ (self):
    return int(self.time)

CREATED_PATHS = set([])
u""" a set of path created by pylot code. This set is used with "safe remove"
commands. Principle is to delete only paths created by ourself.
So, if a path is in created_paths, it can be deleted
"""

# created_paths variable filling
try :
  cp = Utils.readfile(Path(u'~', u'.pylot', u'.cp').expanduser())
except (IOError, OSError) :
  pass
else :
  try : CREATED_PATHS = pickle.load(cp)
  except (EOFError, KeyError) :
    pass

if __name__ == u"__main__":
  import doctest
  doctest.testmod()

